<template>
  <view class="range-slider">
    <view class="bar">
      <view class="point" :style="{ left: `${leftPos}%` }"></view>
      <view class="point" :style="{ left: `${rightPos}%` }"></view>
      <view
        class="active-bar"
        :style="{ left: `${leftPos}%`, width: `${rightPos - leftPos}%` }"
      ></view>
    </view>

    <view
      class="ctrl"
      :style="{ left: `${leftPos}%` }"
      @touchstart="onTouchStart($event, true)"
      @touchmove="onTouchMove($event, true)"
      @touchend="onTouchEnd($event, true)"
      @touchcancel="onTouchEnd($event, true)"
    >
      <image class="image" mode="widthFix" src="/static/images/slider/block.png" />
    </view>
    <view
      class="ctrl"
      :style="{ left: `${rightPos}%` }"
      @touchstart="onTouchStart($event, false)"
      @touchmove="onTouchMove($event, false)"
      @touchend="onTouchEnd($event, false)"
      @touchcancel="onTouchEnd($event, false)"
    >
      <image class="image" mode="widthFix" src="/static/images/slider/block.png" />
    </view>

    <!-- 0 -100 -->
    <view
      class="text"
      :style="{ left: `${leftPos}%`, transform: `translateX(${leftLabelTranslate}%)` }"
    >
      {{ formater(props.modelValue[0], props.min.toString()) }}
    </view>
    <view
      class="text"
      :style="{ left: `${rightPos}%`, transform: `translateX(${rightLabelTranslate}%)` }"
    >
      {{ formater(props.modelValue[1], props.max + '+') }}
    </view>
  </view>
</template>

<script lang="ts" setup>
import { computed, nextTick, onMounted, ref } from 'vue';

const props = withDefaults(
  defineProps<{
    modelValue?: [number?, number?];
    min: number;
    max: number;
    minSpaceRatio?: number;
  }>(),
  {
    modelValue: () => [0, 100],
    min: 0,
    max: 100,
    minSpaceRatio: 0.05,
  },
);

const emits = defineEmits<{
  (event: 'update:modelValue', value: [number?, number?]): void;
}>();

const formater = (value: number | undefined, def: string): string => {
  if (value == undefined) {
    return def;
  }
  return Math.round(value).toString();
};

const leftPos = computed(() => {
  return value2Radio(props.modelValue[0] || props.min) * 100;
});
const rightPos = computed(() => {
  return value2Radio(props.modelValue[1] || props.max) * 100;
});
const leftLabelTranslate = computed(() => {
  const l = value2Radio(props.modelValue[0] || props.min);
  const r = value2Radio(props.modelValue[1] || props.max);
  if (Math.abs(r - l) - props.minSpaceRatio < props.minSpaceRatio * 2) {
    return -(1 - (Math.abs(r - l) - props.minSpaceRatio) / (props.minSpaceRatio * 2)) * 100;
  }
  return 0;
});
const rightLabelTranslate = computed(() => {
  const l = value2Radio(props.modelValue[0] || props.min);
  const r = value2Radio(props.modelValue[1] || props.max);
  if (Math.abs(r - l) - props.minSpaceRatio < props.minSpaceRatio * 2) {
    return -((Math.abs(r - l) - props.minSpaceRatio) / (props.minSpaceRatio * 2)) * 100;
  }
  return -100;
});

const width = ref(0);
const lastLeftX = ref<number | undefined>();
const lastRightX = ref<number | undefined>();

onMounted(() => {
  nextTick(() => {
    updateWidth();
  });
});

const updateWidth = () => {
  const query = uni.createSelectorQuery().in(this);
  query.select('.bar').boundingClientRect((res) => {
    width.value = res.width;
  });
  query.exec();
};

const onTouchStart = (event: TouchEvent, isLeft: boolean) => {
  if (isLeft) {
    lastLeftX.value = event.touches[0].clientX;
  } else {
    lastRightX.value = event.touches[0].clientX;
  }
};

const onTouchMove = (event: TouchEvent, isLeft: boolean) => {
  if (isLeft) {
    const dis = event.touches[0].clientX - lastLeftX.value;
    lastLeftX.value = event.touches[0].clientX;

    const r = dis / width.value;
    let cr = value2Radio(props.modelValue[0] || props.min) + r;

    if (cr < 0) cr = 0;
    if (cr > 1) cr = 1;
    if (cr > value2Radio(props.modelValue[1] || props.max) - props.minSpaceRatio)
      cr = value2Radio(props.modelValue[1] || props.max) - props.minSpaceRatio;

    changeValue(radio2Value(cr), props.modelValue[1] || props.max);
  } else {
    const dis = event.touches[0].clientX - lastRightX.value;
    lastRightX.value = event.touches[0].clientX;

    const r = dis / width.value;
    let cr = value2Radio(props.modelValue[1] || props.max) + r;

    if (cr < 0) cr = 0;
    if (cr > 1) cr = 1;
    if (cr < value2Radio(props.modelValue[0] || props.min) + props.minSpaceRatio)
      cr = value2Radio(props.modelValue[0] || props.min) + props.minSpaceRatio;

    changeValue(props.modelValue[0] || props.min, radio2Value(cr));
  }
};

const onTouchEnd = (event: TouchEvent, isLeft: boolean) => {};

const changeValue = (start?: number, end?: number) => {
  const round = (v?: number) => {
    if (v) {
      return Math.round(v);
    }
    return v;
  };

  emits('update:modelValue', [
    round(start == props.min ? undefined : start),
    round(end == props.max ? undefined : end),
  ]);
};

const radio2Value = (radio: number) => {
  return (props.max - props.min) * radio + props.min;
};

const value2Radio = (value: number) => {
  return (value - props.min) / (props.max - props.min);
};
</script>

<style lang="scss" scoped>
.range-slider {
  position: relative;
  padding: 24px 1px;

  .bar {
    width: 100%;
    height: 2px;
    background: rgba(0, 0, 0, 0.1);
    border-radius: 2px;
    position: relative;

    .point {
      position: absolute;
      left: 0%;
      top: 50%;
      width: 2px;
      height: 8px;
      transform: translate(-50%, -50%);
      background: #fdab27;
      border-radius: 2px;
    }

    .active-bar {
      position: absolute;
      left: 0;
      top: 0;
      height: 100%;
      width: 50%;
      background: #fdab27;
      border-radius: 2px;
    }
  }

  .ctrl {
    position: absolute;
    left: 0%;
    top: calc(50% + 6px);
    width: 14px;
    transform: translateX(-50%);
    .image {
      width: 100%;
    }
  }

  .text {
    position: absolute;
    bottom: calc(50% + 6px);
    text-align: center;
    font-size: 12px;
    color: #999999;
    font-weight: 400;
  }
}
</style>
